/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package org.javaongems.client.winctrls;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HTMLTable.RowFormatter;

/**
 * Known Issues:
 * TODO: set single selection mode
 * TODO: document usage model
 * TODO: remove the additional row being created by the Grid class as a result of inserting a header
 * TODO: sorting and switching between the two views can get mixed up and then elements are not
 * 		 selectable under detail mode
 * TODO: havent worked out the style to make the icons flow in firefox using FlowPanel
 * TODO: add keyboard support
 */

/**
 * <p>Provides a widget that can display grid content in both detail and icon formats.</p>
 * 
 * Styles:  
 * div.gems-ListViewContainer
 * div.gems-ListViewContainer table.gems-ListView
 * div.gems-ListViewContainer table.gems-ListView thead tr th.asc
 * div.gems-ListViewContainer table.gems-ListView thead tr th.desc
 * div.gems-ListViewContainer table.gems-ListView tbody.gems-ListViewScrollContent
 * div.gems-ListViewContainer table.gems-ListView tbody.gems-ListViewScrollContent tr td.sortCol
 * 
 * div.gems-ListViewContainer div.gems-IconView div 
 * div.gems-ListViewContainer div.gems-IconView div img
 * div.gems-ListViewContainer div.gems-IconView div div.caption
 * 
 * @author Ashin Wimalajeewa (ash)
 */
public class ListView extends Composite implements EventListener {
	ListViewDataSource		source;

	ScrollPanel 			shellPanel = new ScrollPanel();
	ListViewIconView		iconView = new ListViewIconView(this);
	ListViewDetailView		detailView = new ListViewDetailView(this);
	ListViewListener		eventHandler;

	ArrayList				selectedIds = new ArrayList();
	ArrayList 				deSelectedIds = new ArrayList(); // volatile

	Event					lastEvent;
	RowComparator 			comparator = new RowComparator();
	int 					comparisonColumn = 0;
	int[] 					sortedRows, dataSrcRows;
	int						lastSortedSelectionPos, lastSelectionId;
	
	boolean 				detailViewMode = false;
	
	public ListView() {
		shellPanel.setWidth("100%");
		initWidget(shellPanel);
		DOM.setAttribute(shellPanel.getElement(), "className", "gems-ListViewContainer");
		shellPanel.add(getView().toWidget());
	}
	
	public void setDataSource(ListViewDataSource src) {
		source = src;
		clearAllSelections();
	}
	
	public void render() {
		getView().render();
	}
	
	public void renderSelections() {
		getView().renderSelections();
	}
	
	public List getSelectedIds() {
		return selectedIds;
	}
	
	public List getDeselectedIds() {
		return deSelectedIds;
	}
	
	public void clearAllSelections() {
		selectedIds.clear();
		deSelectedIds.clear();
	}
	
	public void onResize(int width, int height) {
		shellPanel.setPixelSize(width, height);
		getView().setPixelSize(width, height-23);
	}
	
	public ListViewDetailViewStyle getDetailViewStyle() {
		return detailView.formatStyler;
	}
	
	public void setDetailViewStyle(ListViewDetailViewStyle adj) {
		detailView.formatStyler = adj;
	}

	public ListViewColumnHeaderFormatter getColumnHeaderFormatter() {
		return detailView.headerFormatter;
	}
	
	public CellFormatter getCellFormatter() {
		return detailView.getCellFormatter();
	}
	
	public RowFormatter getRowFormatter() {
		return detailView.getRowFormatter();
	}

	public void setViewMode(boolean detail) {
		if (detailViewMode == detail)
			return;
		Widget in, out;
		if (detail) {
			in = detailView.toWidget();
			out = iconView.toWidget();
		} else {
			in = iconView.toWidget();
			out = detailView.toWidget();
		}
		shellPanel.remove(out);
		shellPanel.add(in);
		detailViewMode = detail; 
	}
	
	public boolean getDetailViewMode() {
		return detailViewMode;
	}
	
	public void setViewListener(ListViewListener toSet) {
		eventHandler = toSet;
	}
	
    private ListViewDataSourceView getView() {
        if(detailViewMode)
            return detailView;
        else
            return iconView;
    }

    protected void prepareSortMappers(int rowSz) {
		sortedRows = new int[rowSz];
		dataSrcRows = new int[rowSz];
	}	
	
	protected void sortData(int rowSz) {
		ArrayList tmp = new ArrayList();
		for (int i = 0; i < rowSz; i++) {
			RowSortElem elm = new RowSortElem(i, source.getComparableCellData(i, comparisonColumn));
			tmp.add(elm);
		}
		Collections.sort(tmp, comparator);
		for (int i = 0; i < rowSz; i++) {
			int realRow = ((RowSortElem)tmp.get(i)).dsRow;
			sortedRows[i] = realRow;
			dataSrcRows[realRow] = i;
			if (realRow == lastSelectionId)
				lastSortedSelectionPos = i;
		}
	}
	
	void handleDataRowSelection(int dataRowId, int sortedRow) {
		Integer newSel = new Integer(dataRowId);
		boolean shift = DOM.eventGetShiftKey(lastEvent);
		boolean ctrl = DOM.eventGetCtrlKey(lastEvent);
		if (ctrl)
			handleCtrlSelection(newSel);
		else if (shift)
			handleShiftSelection(newSel, sortedRow);
		else
			handleBasicSelection(newSel);
		lastSelectionId = dataRowId;
		lastSortedSelectionPos = sortedRow;
		renderSelections();
		deSelectedIds.clear();
	}
	
	private void handleBasicSelection(Integer newSel) {
		deSelectedIds.addAll(selectedIds);
		selectedIds.clear();
		selectedIds.add(newSel);
	}
	
	private void handleShiftSelection(Integer newSel, int row) {
		int low = Math.min(lastSortedSelectionPos, row);
		int high = Math.max(lastSortedSelectionPos, row);
		deSelectedIds.addAll(selectedIds);
		selectedIds.clear();
		for (int i = low; i <= high; i++) {
			Integer newId = new Integer(sortedRows[i]);
			selectedIds.add(newId);
		}
	}
	
	private void handleCtrlSelection(Integer newSel) {
		if (!selectedIds.remove(newSel)) {
			selectedIds.add(newSel);
		} else
			deSelectedIds.add(newSel);
	}
	
	static class RowSortElem {
		public int 			dsRow;
		public Comparable 	dsData;
		public RowSortElem(int row, Comparable data) { dsRow = row; dsData = data;}  
	}
	
	static class RowComparator implements Comparator {
		public boolean descOrder = false;
		public int compare(Object arg0, Object arg1) {
			RowSortElem first = (RowSortElem) arg0;
			RowSortElem second = (RowSortElem) arg1;
			int val = first.dsData.compareTo(second.dsData);
			return (descOrder ? -val: val);
		}
	}
}